Bingo, Computer Graphics & Game Developer

Perspective-Correct Interpolation

MVP矩阵推导

其中Model, View的矩阵部分可根据Local转World坐标系转换得到,可参考龙书5.6.1章节。

透视投影矩阵可以分为x,yx, y的投影变换,以及zz的透视校正
首先x,yx, y部分可以根据透视变换的相似三角形推导出,此处xx部分的FOV令为θ\theta,横纵坐标比aspect令为r=whr=\frac{w}{h}

Projection

对应x,yx, y的转换细节推导可参考透视投影详解

其中(3)(3)中的x,yx'',y''已经归一化转化到了NDC空间(注意要用θ2\frac{\theta}{2}

x=xzcot(θ2)1ry=yzcot(θ2)
\begin{array}{l}
x''=\frac{x}{z}cot(\frac{\theta}{2})\frac{1}{r} \\
y''=\frac{y}{z}cot(\frac{\theta}{2})
\end{array}

此时zz没有做过变换,本质上若需要归一化,也只需要做一个从区间[n,f][n,f][0,1][0,1]的一个变换即可。但若zz仅仅只是做了一个区间映射而没有经过透视校正,则会出现下述问题。


interpolation

  1. Vertex Shader完成顶点变换后需要做光栅化步骤,此时会在投影平面上进行均匀采样。根据上图中,对屏幕上线段进行均匀采样,而其对应线段中的点并非均匀排布。观察发现对应线段上点的x,yx,y部分仍然保持均匀分布,但zz由于透视变换的问题造成结果的畸变。

  2. 后续的纹理,颜色插值计算也会因为zz轴的透视问题带来非均匀分布的问题

本质上的问题是因为zz在透视变换后,是非线性分布的。因此对应的解决方案就是找到一个能够满足深度前后关系不变,且能支持线性插值的量来代替原有的zz

201211251321255139

此处证明1z\frac{1}{z}是线性分布的。令直线为ax+bz=cax+bz=c,给出线段上一点[x,z][x, z],其对应投影平面上的点为[p,e][p, -e],即有

px=ez(ape+b)z=c1z=apce+bc
\begin{array}{l}
\frac{p}{x} = \frac{-e}{z} \\
(-\frac{ap}{e}+b)z = c \\
\frac{1}{z} = -\frac{ap}{ce}+\frac{b}{c}
\end{array}

设线段的头尾点为[x1,z1],[x2,z2][x_1, z_1], [x_2, z_2]以及对应投影平面上的点为[p1,e],[p2,e][p_1,-e],[p_2,-e]。令p3=(1t)p1+tp2p_3=(1-t)p_1+tp_2p1,p2p_1,p_2线性插值得出,求[p3,e][p_3,-e]对应的线段上的点。

1z3=ap3ce+bc=ap1ce(1t)ap2cet+bc=(ap1ce+bc)(1t)+(ap2ce+bc)t=1z1(1t)+1z2t
\begin{array}{l}
\frac{1}{z_3} & = & -\frac{ap_3}{ce}+\frac{b}{c} \\
& = & -\frac{ap_1}{ce}(1-t)–\frac{ap_2}{ce}t+\frac{b}{c} \\
& = & (-\frac{ap_1}{ce}+\frac{b}{c})(1-t)+(-\frac{ap_2}{ce}+\frac{b}{c})t \\
& = & \frac{1}{z_1}(1-t)+\frac{1}{z_2}t
\end{array}

可见1z\frac{1}{z}在直线上可以线性插值。由于zz轴在光栅化中需要被插值,因此直接构造1z\frac{1}{z}使得硬件在做插值时保持线性,为了将zz'归一化到[0,1][0,1]需要构造z=A+Bz+Bz'=A+\frac{B}{z}+B。已知

0=A+Bn1=A+Bf
\begin{array}{l}
0 & = & A+\frac{B}{n} \\
1 & = & A+\frac{B}{f}
\end{array}

求解方程组,则有A=ffn,B=nffnA=\frac{f}{f-n}, B=\frac{nf}{f-n}

可得最终透视投影矩阵

这里推导的结果为左手系。若左右手系区分则只需要调换部分元素位置,可见龙书推导。

[1rtan(θ2)00001tan(θ2)0000ffn100nffn0]
\left[
\begin{matrix}
\frac{1}{rtan(\frac{\theta}{2})} & 0 & 0 & 0 \
0 & \frac{1}{tan(\frac{\theta}{2})} & 0 & 0 \\
0 & 0 & \frac{f}{f-n} & 1 \\
0 & 0 & \frac{nf}{f-n} & 0
\end{matrix}
\right]

Sophia Renderer上的矩阵

由于DirectX 11的GPU部分采用了列主的矩阵排布。一般有两种解决方案

  1. CPU端采用行主,GPU端采用列主,在CPU绑定矩阵到CB的时候转置一次。
  2. CPU端采用行主不变,GPU端直接左乘向量(GPU上的矩阵是CPU的转置)

采用两种方式不同的地方在于,前者MVP=ProjectionViewWorldVectorMVP=Projection View World Vector,后者本质上是MVP=VectorWorldTViewTProjectionTMVP = Vector World^T View^T Projection^T,而由于列主是默认的,因此在GPU上看起来就像MVP=VWVPMVP = V W V * P一样。本质上只是CPU端的转置结果。

这里Sophia Renderer采用第二种方式,即默认GPU端都为CPU端绑定结果的转置,因此选用左乘来得到正确结果。

Reference

  1. Mathematics for 3D Game Programming and Computer Graphics, Third Edition, Section 5.4
  2. Introduction to 3D GAME PROGRAMMING WITH DIRECTX® 11, Section 5.6